可以使用enable\_if<>来解决在6.2节中引入的构造函数模板的问题。

必须解决的问题是，禁用模板构造函数的声明

\begin{lstlisting}[style=styleCXX]
template<typename STR>
Person(STR&& n);
\end{lstlisting}

若传递的参数STR具有正确的类型(即std::string，或可转换为std::string的类型)。

为此，使用另一个标准类型std::is\_convertible<FROM,TO>。C++17中，相应的声明如下所示:

\begin{lstlisting}[style=styleCXX]
template<typename STR,
	typename = std::enable_if_t<
		std::is_convertible_v<STR, std::string>>>
Person(STR&& n);
\end{lstlisting}

如果STR类型可转换为std::string类型，则整个声明展开为

\begin{lstlisting}[style=styleCXX]
template<typename STR,
	typename = void>
Person(STR&& n);
\end{lstlisting}

如果STR类型不能转换为std::string类型，将忽略整个函数模板。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}为什么不检查STR是否“不可转换为Person”？我们正在定义一个函数，该函数可能允许将字符串转换为Person。因此构造函数必须知道它是否启用，这取决于是否可转换，而可转换取决于是否启用，循环往复。永远不要在影响enable\_if使用的条件的地方使用enable\_if。这是编译器不一定能检测到的逻辑错误。
\end{tcolorbox}

同样，可以使用别名模板定义名称:

\begin{lstlisting}[style=styleCXX]
template<typename T>
using EnableIfString = std::enable_if_t<
						std::is_convertible_v<T, std::string>>;
...
template<typename STR, typename = EnableIfString<STR>>
Person(STR&& n);
\end{lstlisting}

因此，Person类如下所示:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{basics/specialmemtmpl3.hpp}
\begin{lstlisting}[style=styleCXX]
#include <utility>
#include <string>
#include <iostream>
#include <type_traits>

template<typename T>
using EnableIfString = std::enable_if_t<
						std::is_convertible_v<T,std::string>>;

class Person
{
private:
	std::string name;
public:
	// generic constructor for passed initial name:
	template<typename STR, typename = EnableIfString<STR>>
	explicit Person(STR&& n)
	: name(std::forward<STR>(n)) {
		std::cout << "TMPL-CONSTR for '" << name << "'\n";
	}

	// copy and move constructor:
	Person (Person const& p) : name(p.name) {
		std::cout << "COPY-CONSTR Person '" << name << "'\n";
	}
	Person (Person&& p) : name(std::move(p.name)) {
		std::cout << "MOVE-CONSTR Person '" << name << "'\n";
	}
};
\end{lstlisting}

现在，所有行为都符合预期:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{basics/specialmemtmpl3.cpp}
\begin{lstlisting}[style=styleCXX]
#include "specialmemtmpl3.hpp"

int main()
{
	std::string s = "sname";
	Person p1(s); // init with string object => calls TMPL-CONSTR
	Person p2("tmp"); // init with string literal => calls TMPL-CONSTR
	Person p3(p1); // OK => calls COPY-CONSTR
	Person p4(std::move(p1)); // OK => calls MOVE-CONST
}
\end{lstlisting}

C++14中，必须如下声明别名模板，因为\_v版本没有为类型定义value:

\begin{lstlisting}[style=styleCXX]
template<typename T>
using EnableIfString = std::enable_if_t<
						std::is_convertible<T, std::string>::value>;
\end{lstlisting}

C++11中，必须声明特殊成员模板，因为\_t版本并没有定义type:

\begin{lstlisting}[style=styleCXX]
template<typename T>
using EnableIfString
= typename std::enable_if<std::is_convertible<T, std::string>::value
						>::type;
\end{lstlisting}

但这些现在都隐藏在EnableIfString<>的定义中。

因为它要求类型是隐式可转换的，所以使用std::is\_convertible<>还有另一种选择。通过使用std::is\_constructible<>，允许显式转换用于初始化。然而，在这种情况下，参数的顺序是反的:

\begin{lstlisting}[style=styleCXX]
template<typename T>
using EnableIfString = std::enable_if_t<
						std::is_constructible_v<std::string, T>>;
\end{lstlisting}

关于std::is\_constructible<>和std::is\_convertible<>的详细信息请参见D.3.2节。关于在可变参数模板上应用enable\_if<>的详细信息和示例，请参见D.6节。

\hspace*{\fill} \\ %插入空行
\noindent
\textbf{禁用特殊成员函数}

注意，通常不能使用enable\_if<>来禁用预定义的复制/移动构造函数和/或赋值操作符。原因是成员函数模板永远不会算作特殊成员函数，并且在需要复制构造函数等情况下会忽略。因此，在此声明:

\begin{lstlisting}[style=styleCXX]
class C {
public:
	template<typename T>
	C (T const&) {
		std::cout << "tmpl copy constructor\n";
	}
	...
};
\end{lstlisting}

当请求一个C的副本时，预定义的复制构造函数仍然可以使用:

\begin{lstlisting}[style=styleCXX]
C x;
C y{x}; // still uses the predefined copy constructor (not the member template)
\end{lstlisting}

(实际上没有办法使用成员模板，因为没有办法指定或推导它的模板参数T。)

删除预定义的复制构造函数不是解决方案，因为复制C的尝试将导致错误。

不过，有一个解决方案:

可以为const volatile参数声明复制构造函数，并将其标记为“已删除”(即用= delete修饰)。这样做可以防止隐式声明另一个复制构造函数。有了这些，就可以定义一个构造函数模板，对于非volatile类型，该构造函数将优先于(已删除的)复制构造函数:

\begin{lstlisting}[style=styleCXX]
class C
{
public:
	...
	// user-define the predefined copy constructor as deleted
	// (with conversion to volatile to enable better matches)
	C(C const volatile&) = delete;
	
	// implement copy constructor template with better match:
	template<typename T>
	C (T const&) {
		std::cout << "tmpl copy constructor\n";
	}
...
};
\end{lstlisting}

现在模板构造函数会用于“通常”的复制:

\begin{lstlisting}[style=styleCXX]
C x;
C y{x}; // uses the member template
\end{lstlisting}

在模板构造函数中，可以使用enable\_if<>进行约束。例如，若模板参数是整型，为了防止复制类模板C<>的对象，可以实现以下方法:

\begin{lstlisting}[style=styleCXX]
template<typename T>
class C
{
public:
	...
	// user-define the predefined copy constructor as deleted
	// (with conversion to volatile to enable better matches)
	C(C const volatile&) = delete;
	// if T is no integral type, provide copy constructor template with better match:
	template<typename U,
	typename = std::enable_if_t<!std::is_integral<U>::value>>
	C (C<U> const&) {
		...
	}
	...
};
\end{lstlisting}












